package com.gupaoedu.spring.framework.context;

import com.gupaoedu.spring.framework.annotation.GPAutowired;
import com.gupaoedu.spring.framework.annotation.GPController;
import com.gupaoedu.spring.framework.annotation.GPService;
import com.gupaoedu.spring.framework.beans.GPBeanWrapper;
import com.gupaoedu.spring.framework.beans.config.GPBeanDefinition;
import com.gupaoedu.spring.framework.beans.support.GPBeanDefinitionReader;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author fan
 * @version 1.0v
 * @Description TODO
 * @date 2021-01-03 19:36
 * @email 44867924@qq.com
 */
public class GPApplicationContext {
    private String[] configLocations;
    private GPBeanDefinitionReader reader;

    private Map<String, GPBeanDefinition> beanDefinitionMap = new HashMap<>();

    private Map<String, GPBeanWrapper> factoryBeanInstanceCache = new HashMap<>();

    /**
     * 这是用来装原生对象的
     */
    private Map<String, Object> factoryBeanObjectCache = new HashMap<>();

    public GPApplicationContext(String... configLocations) {
        this.configLocations = configLocations;

        try {
            // 1读取配置文件，并解析成BeanDefinition对象
            /**
             * 读取配置文件不是　ApplicationContext　的事，所以由BeanDefinitionReader　来完成
             * configLocations　：　[“application.properties”]　这里是一个字符串数组
             */
            reader = new GPBeanDefinitionReader(configLocations);
            List<GPBeanDefinition> beanDefinitions = reader.loadBeanDefinitons();

            // 2将解析后的BeanDefinition对象缓存到BeanDefinitionMap中
            doRegisterBeanDefination(beanDefinitions);

            // 3触发创建对象的动作，调用getBean()方法
            doCreateBean();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doCreateBean() {
        for (Map.Entry<String, GPBeanDefinition> beanDefinitionEntry : beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            //真正触发ioc 和　di的动作
            /*
             * getBean的两个作用
             * 1 创建对象
             * 2依赖注入
             */
            getBean(beanName);
        }
    }

    /**
     * 将解析后的BeanDefinition对象缓存到BeanDefinitionMap中
     * @param beanDefinitions 实体类+接口　集合
     * @throws Exception
     */
    private void doRegisterBeanDefination(List<GPBeanDefinition> beanDefinitions) throws Exception {
        for (GPBeanDefinition beanDefinition : beanDefinitions) {
            if (beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("this" + beanDefinition.getFactoryBeanName() + "is exists");
            }
            beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
            // 为了丰富
            beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
        }
    }

    public Object getBean(Class beanClass) {
        return getBean(beanClass.getName());
    }

    public Object getBean(String beanName) {
        // ============== 创建实例  =============
        // 1　获取配置信息　只要拿到BeanDefinition 对象即可
        GPBeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        // 利用反射创建实例
        // 实例可能是原生对象也可能是代理对象
        Object instance = instaniateBean(beanName, beanDefinition);
        GPBeanWrapper beanWrapper = new GPBeanWrapper(instance);
        factoryBeanInstanceCache.put(beanName, beanWrapper);

        //============ 依赖注入 =========
        populateBean(beanName, beanDefinition, beanWrapper);

        return this.factoryBeanInstanceCache.get(beanName).getWrapperInstance();
    }

    private void populateBean(String beanName, GPBeanDefinition beanDefinition, GPBeanWrapper beanWrapper) {
        Object instance = beanWrapper.getWrapperInstance();
        Class<?> clazz = beanWrapper.getWrapperClass();
        //　只有加了注解的类才需要注入
        if (!(clazz.isAnnotationPresent(GPController.class) || clazz.isAnnotationPresent(GPService.class))) {
            return;
        }

//        // ioc是里面所有的加了注解的对应的类
//        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
        // 得到所有字段，　包括　public priviate protected default
        //  Field[] fields = entry.getValue().getClass().getDeclaredFields();
        for (Field field : clazz.getFields()) {
            // 只有加了这个注解的，才给他自动赋值
            if (!field.isAnnotationPresent(GPAutowired.class)) {
                continue;
            }
            GPAutowired autowired = field.getAnnotation(GPAutowired.class);
            // 看有没有自定义的
            String autowiredBeanName = autowired.value().trim();
            if ("".equals(autowiredBeanName)) {
                autowiredBeanName = field.getType().getName();
            }

            //　强制访问，
            field.setAccessible(true);
            try {
                if (this.factoryBeanInstanceCache.get(autowiredBeanName) == null) {
                    continue;
                }
                    /*
                     filed 相当于　
                     @GPAutowired
                     private IDemoService demoService
                     */
                // entry.getValue() ===> DemoAction 实例
                // ioc.get(beanName) ===>ioc.get( com.gupaoedu.mvcframework.servlet.IDemoService)
                System.out.println(beanName);
                // 这里得到的结果为null
                // System.out.println(ioc.get(beanName));
                //field.set(entry.getValue(), ioc.get(beanName));
                field.set(instance, this.factoryBeanInstanceCache.get(beanName).getWrapperInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    private Object instaniateBean(String beanName, GPBeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        Object instance = null;

        try {
            // 这里是原生对象
            Class<?> clazz = Class.forName(className);
            instance = clazz.newInstance();

            //如果要代理　,aop com.gupaoedu.demo..*.*service
            // 接入　aop
            this.factoryBeanObjectCache.put(beanName, instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }

    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }
}
